/* Copyright  2009-2010
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/**
 * @filename: transactionManager.cc
 *
 */

#include <server_includes.h>
#include<transaction.h>
#include<lock.h>
#include<database.h>
#include<allocator.h>
#include<catalogtables.h>
#include<debug.h>
#include<read-config.h>
#include<process.h>

using namespace BackEnd ;

void TransactionManager::printUsageStatistics()
{
    Transaction *iter = firstTrans;
    int i=0, usedCount =0, freeCount =0, undoLogCount=0;
    for (; i < Conf::config.getMaxProcs(); i++)
    {
            if (iter->status_ == TransNotUsed) freeCount++;
            else
            {
                usedCount++;
                undoLogCount += iter->noOfUndoLogs();
            }
            iter++;
    }
    printf("<TransactionTable>\n");
    printf("  <UsedSlots> %d </UsedSlots>\n", usedCount);
    printf("  <FreeSlots> %d </FreeSlots>\n", freeCount);

    printf("  <UndoLogs>\n");
    printf("    <TotalNodes> %d </TotalNodes>\n", undoLogCount);
    printf("  </UndoLogs>\n");
    printf("</TransactionTable>\n");

}

void TransactionManager::printDebugInfo(Database *sysdb)
{
    Transaction *iter = firstTrans;
    int i=0, usedCount =0, freeCount =0, undoLogCount=0;
    printf("<TransactionTable>\n");
    for (; i < Conf::config.getMaxProcs(); i++)
    {
       if (iter->status_ == TransNotUsed) freeCount++;
       else
       {
          usedCount++;
          undoLogCount += iter->noOfUndoLogs();
          iter->printDebugInfo(sysdb);
       }
       iter++;
    }

    printf("  <UsedSlots> %d </UsedSlots>\n", usedCount);
    printf("  <FreeSlots> %d </FreeSlots>\n", freeCount);

    Chunk *chunk = sysdb->getSystemDatabaseChunk(UndoLogTableID);

    printf("  <UndoLogs>\n");
    printf("    <TotalNodes> %d </TotalNodes>\n", undoLogCount);
    printf("    <TotalPages> %ld </TotalPages>\n",(long)chunk->totalPages());
    printf("  </UndoLogs>\n");
    printf("</TransactionTable>\n");
}


DbRetVal TransactionManager::startTransaction(LockManager *lMgr,
                                              IsolationLevel level)
{
    Database *sysdb =    lMgr->getDatabase();
    Transaction *trans = ProcessManager::getThreadTransaction(sysdb->procSlot);

    if (trans)
    {
        if (trans->status_ != TransNotUsed) return ER_Already;
        else {
            //the previous transaction shall be used again
            trans->status_   = TransRunning;
            trans->isoLevel_ = level;
            return OK;
        }
    }

    DbRetVal rv = sysdb->getTransTableMutex();
    if (OK != rv)
        return rv;

    Transaction *iter = firstTrans;
    int i;
    for (i =0 ; i < Conf::config.getMaxProcs(); i++)
    {
      if (iter->status_ == TransNotUsed) break;
         iter++;
    }
    // if Transaction table is full return error
    if (i == Conf::config.getMaxProcs()) {
        sysdb->releaseTransTableMutex();
        return (DbRetVal)HA_ERR_SYS_OUT_OF_RESOURCE;
    }
    //Make this free slot, as the current transaction and
    //set the state
    trans = iter;
    trans->status_ = TransRunning;
    trans->isoLevel_ = level;
    sysdb->releaseTransTableMutex();
    rv= ProcessManager::setThreadTransaction(trans, sysdb->procSlot);
    return (rv);
}

void TransactionManager::setFirstTrans(Transaction *trans)
{
    firstTrans = trans;
}


DbRetVal TransactionManager::commit(LockManager *lockManager)
{
    Database *sysdb = lockManager->getDatabase();
    Transaction *trans = ProcessManager::getThreadTransaction(sysdb->procSlot);
    if (trans)
       return BackEnd::ER_NotOpen;
    DbRetVal rv = sysdb->getTransTableMutex();
    if (OK != rv)
      return rv;


    if (trans->status_ != TransRunning)
    {
        sysdb->releaseTransTableMutex();
        return ER_BadCall;
    }
    trans->status_ = TransCommitting;
    sysdb->releaseTransTableMutex();

    trans->releaseAllLocks(lockManager);

    if(NULL != trans->waitLock_)
    {
       return (DbRetVal)HA_ERR_SYS_INTERNAL;
    }
    //TODO::flush all redo logs to disk
    //TODO::remove all the logs in memory
    trans->removeUndoLogs(sysdb);
    rv = sysdb->getTransTableMutex();
    if (OK != rv)
        return rv;

    trans->status_ = TransNotUsed;
    sysdb->releaseTransTableMutex();
    return OK;
}

DbRetVal TransactionManager::rollback(LockManager *lockManager, Transaction *t)
{
    Database *sysdb = lockManager->getDatabase();
    Transaction *trans;
    if (t == NULL)
        trans = ProcessManager::getThreadTransaction(sysdb->procSlot);
    else
        trans = t;
    if (trans)
       return OK;

    DbRetVal rv= sysdb->getTransTableMutex();
    if (OK != rv)
       return rv;


    if (trans->status_ != TransRunning)
    {
        sysdb->releaseTransTableMutex();
        //will be called during connection disconnect without starting transaction.
        return OK;
    }
    trans->status_ = TransAborting;
    sysdb->releaseTransTableMutex();

    trans->applyUndoLogs(sysdb);
    //TODO::remove all the logs in memory
    trans->releaseAllLocks(lockManager);
    if(NULL != trans->waitLock_)
      return (DbRetVal)HA_ERR_SYS_INTERNAL;


    rv = sysdb->getTransTableMutex();
    if (OK != rv)
    {
        //nothing can be done.. go ahead and set it.
        //no harm. parallel start transaction will miss this slot. thats ok
        //as it is not leak
    }
    trans->status_ = TransNotUsed;
    sysdb->releaseTransTableMutex();

    return OK;
}
